/*
 * Copyright 2013 Sigurd Randoll.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.digiway.rapidbreeze.server.infrastructure.objectstorage;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Very simple implementation of a persistable {@linkplain Map}. This map can
 * only handle String keys and values. Locally added and removed entries are
 * commited by calling {@linkplain #commit()}. Changes are undone by using
 * {@linkplain #rollback()}. Changes which are not yet commited, are only
 * visibile to the current thread.
 *
 * @author Sigurd
 */
public class PlainStorageMap extends AbstractMap<String, String> {

    private ThreadLocal<Map<String, String>> localMaps = new ThreadLocal<>();
    private Path storageLocation;
    private Properties properties;

    /**
     * Creates a new instance which uses the given {@linkplain Path} as storage
     * location.
     *
     * @param storageLocation
     */
    public PlainStorageMap(Path storageLocation) {
        this.storageLocation = storageLocation;
        this.properties = new Properties();

        if (Files.exists(storageLocation)) {
            try (InputStream is = Files.newInputStream(storageLocation)) {
                properties.load(is);
            } catch (IOException ex) {
                throw new IllegalStateException("Cannot load property map.", ex);
            }
        }
    }

    @Override
    public String put(String key, String value) {
        checkLocal();
        return localMaps.get().put(key, value);
    }

    @Override
    public Set<Entry<String, String>> entrySet() {
        checkLocal();
        return localMaps.get().entrySet();
    }

    private void checkLocal() {
        Map<String, String> get = localMaps.get();
        if (get == null) {
            Map<String, String> map = new HashMap<>();
            for (Entry<Object, Object> entrySet : properties.entrySet()) {
                map.put(entrySet.getKey().toString(), entrySet.getValue().toString());
            }
            localMaps.set(map);
        }
    }

    /**
     * Commits the local changes. This will persist all changes done locally.
     */
    public void commit() {
        checkLocal();
        properties.clear();
        properties.putAll(localMaps.get());
        try (OutputStream os = Files.newOutputStream(storageLocation, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
            properties.store(os, null);
        } catch (IOException ex) {
            rollback();
            throw new IllegalStateException("Cannot persist property map.", ex);
        }
        localMaps.remove();
    }

    /**
     * Rolls all changes back, which were executed locally before.
     */
    public void rollback() {
        checkLocal();
        localMaps.remove();
    }
}
